/* $Id: VBoxMPVModes.cpp $ */
/** @file
 * VBox WDDM Miniport driver
 */

/*
 * Copyright (C) 2014-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

#include <stdio.h> /* for swprintf */
#include <winerror.h>

#include "VBoxMPWddm.h"
#include "VBoxMPVModes.h"
#include "trace.h"
#include "VBoxMPRegistry.h"
#include "common/VBoxMPUtils.h"
#include "gallium/SvgaHw.h"
#include "gallium/VBoxMPGaExt.h"

#define DEFAULT_WIDTH_3_2 1920 // 3:2 ratio default width
#define DEFAULT_HEIGHT_3_2 1280 // 3:2 ratio default height
#define DEFAULT_WIDTH_4_3 1920 // 4:3 ratio default width
#define DEFAULT_HEIGHT_4_3 1440 // 4:3 ratio default height
#define DEFAULT_WIDTH_103_67 1854 // 103:67 ratio default width
#define DEFAULT_HEIGHT_103_67 1206 // 103:67 ratio default height

int VBoxVModesInit(VBOX_VMODES *pModes, UINT32 cTargets)
{
    if (cTargets >= VBOX_VIDEO_MAX_SCREENS)
    {
        LOGE("invalid target\n");
        return VERR_INVALID_PARAMETER;
    }

    pModes->cTargets = cTargets;
    for (UINT32 i = 0; i < cTargets; ++i)
    {
        int rc = CrSaInit(&pModes->aTargets[i], 16);
        if (RT_FAILURE(rc))
        {
            LOGW("CrSaInit failed\n");

            for (UINT32 j = 0; j < i; ++j)
            {
                CrSaCleanup(&pModes->aTargets[j]);
            }
            return rc;
        }
    }

    return VINF_SUCCESS;
}

void VBoxVModesCleanup(VBOX_VMODES *pModes)
{
    for (UINT32 i = 0; i < pModes->cTargets; ++i)
    {
        CrSaCleanup(&pModes->aTargets[i]);
    }
}

int VBoxVModesAdd(VBOX_VMODES* pModes, UINT32 u32Target, UINT64 u64)
{
    if (u32Target >= pModes->cTargets)
    {
        LOGE("invalid target id\n");
        return VERR_INVALID_PARAMETER;
    }

    return CrSaAdd(&pModes->aTargets[u32Target], u64);
}

int VBoxVModesRemove(VBOX_VMODES* pModes, UINT32 u32Target, UINT64 u64)
{
    if (u32Target >= pModes->cTargets)
    {
        LOGE("invalid target id\n");
        return VERR_INVALID_PARAMETER;
    }

    return CrSaRemove(&pModes->aTargets[u32Target], u64);
}

static void vboxWddmVModesInit(VBOXWDDM_VMODES *pModes, UINT32 cTargets)
{
    VBoxVModesInit(&pModes->Modes, cTargets);
    memset(pModes->aTransientResolutions, 0, cTargets * sizeof (pModes->aTransientResolutions[0]));
    memset(pModes->aPendingRemoveCurResolutions, 0, cTargets * sizeof (pModes->aPendingRemoveCurResolutions[0]));
}

static void vboxWddmVModesCleanup(VBOXWDDM_VMODES *pModes)
{
    VBoxVModesCleanup(&pModes->Modes);
    memset(pModes->aTransientResolutions, 0, sizeof (pModes->aTransientResolutions));
    memset(pModes->aPendingRemoveCurResolutions, 0, sizeof (pModes->aPendingRemoveCurResolutions));
}

static RTRECTSIZE g_VBoxBuiltinResolutions_mixed[] =
{
    /* standard modes */
    { 800,   600 },
    { 1024,  768 },
    { 1280,  960 },
    { 1920, 1440 },
    { 2160, 1620 },
    { 2560, 1920 },
    { 1280,  720 },
    { 1440,  960 },
    { 1680, 1120 },
    { 1920, 1280 },
    { 2160, 1440 },
};

static RTRECTSIZE g_VBoxBuiltinResolutions_43[] =
{
    { 2160, 1620 },
    { 1920, 1440 },
    { 1680, 1260 },
    { 1440, 1080 },
};

static RTRECTSIZE g_VBoxBuiltinResolutions_32[] =
{
    { 2160, 1440 },
    { 1920, 1280 },
    { 1680, 1120 },
    { 1440,  960 },
};

static RTRECTSIZE g_VBoxBuiltinResolutions_half[] = 
{
    { 2060, 1340 },
    { 1854, 1206 },
    { 1648, 1072 },
    { 1442,  938 },
};

static RTRECTSIZE* g_VBoxBuiltinResolutions = g_VBoxBuiltinResolutions_mixed;
static UINT32 g_VBoxBuiltinResolutionsSize = RT_ELEMENTS(g_VBoxBuiltinResolutions_mixed);

void vboxInitResolutionsWithSurfaceSize(UINT32 surface_width, UINT surface_height) {
    if (surface_width == 0 || surface_height == 0) {
        LOGE("Invalid surface size, use mixed resolutions: width=%u, height=%u\n", surface_width, surface_height);
        return;
    }
    if (surface_width * 2 / 3 == surface_height) {
        LOGE("Use 3:2 resolutions, surface size: width=%u, height=%u\n", surface_width, surface_height);
        g_VBoxBuiltinResolutions = g_VBoxBuiltinResolutions_32;
        g_VBoxBuiltinResolutionsSize = RT_ELEMENTS(g_VBoxBuiltinResolutions_32);
    } else if (surface_width * 3 / 4 == surface_height) {
        LOGE("Use 4:3 resolutions, surface size: width=%u, height=%u\n", surface_width, surface_height);
        g_VBoxBuiltinResolutions = g_VBoxBuiltinResolutions_43;
        g_VBoxBuiltinResolutionsSize = RT_ELEMENTS(g_VBoxBuiltinResolutions_43);
    } else if (surface_width * 1608 / 2472 == surface_height) {
        LOGE("Use 103:67 screen resolutions, surface size: width=%u, height=%u\n", surface_width, surface_height);
        g_VBoxBuiltinResolutions = g_VBoxBuiltinResolutions_half;
        g_VBoxBuiltinResolutionsSize = RT_ELEMENTS(g_VBoxBuiltinResolutions_half);
    }
}

DECLINLINE(BOOLEAN) vboxVModesRMatch(const RTRECTSIZE* pResolution1, const RTRECTSIZE* pResolution2)
{
    return !memcmp(pResolution1, pResolution2, sizeof(*pResolution1));
}

int vboxWddmVModesRemove(PVBOXMP_DEVEXT pExt, VBOXWDDM_VMODES* pModes, UINT32 u32Target, const RTRECTSIZE* pResolution)
{
    if (!pResolution->cx || !pResolution->cy)
    {
        LOGE("invalid resolution data\n");
        return VERR_INVALID_PARAMETER;
    }

    if (u32Target >= pModes->Modes.cTargets)
    {
        LOGE("invalid target id\n");
        return VERR_INVALID_PARAMETER;
    }

    if (CR_RSIZE2U64(*pResolution) == pModes->aTransientResolutions[u32Target])
        pModes->aTransientResolutions[u32Target] = 0;

    if (vboxVModesRMatch(pResolution, &pExt->aTargets[u32Target].Size))
    {
        if (CR_RSIZE2U64(*pResolution) == pModes->aPendingRemoveCurResolutions[u32Target])
            return VINF_ALREADY_INITIALIZED;

        if (pModes->aPendingRemoveCurResolutions[u32Target])
        {
            VBoxVModesRemove(&pModes->Modes, u32Target, pModes->aPendingRemoveCurResolutions[u32Target]);
            pModes->aPendingRemoveCurResolutions[u32Target] = 0;
        }

        pModes->aPendingRemoveCurResolutions[u32Target] = CR_RSIZE2U64(*pResolution);
        return VINF_ALREADY_INITIALIZED;
    }
    else if (CR_RSIZE2U64(*pResolution) == pModes->aPendingRemoveCurResolutions[u32Target])
        pModes->aPendingRemoveCurResolutions[u32Target] = 0;

    int rc = VBoxVModesRemove(&pModes->Modes, u32Target, CR_RSIZE2U64(*pResolution));
    if (RT_FAILURE(rc))
    {
        LOGW("VBoxVModesRemove failed %d, can never happen\n", rc);
        return rc;
    }

    if (rc == VINF_ALREADY_INITIALIZED)
        return rc;

    return VINF_SUCCESS;
}

static void vboxWddmVModesSaveTransient(PVBOXMP_DEVEXT pExt, UINT32 u32Target, const RTRECTSIZE* pResolution)
{
    VBOXMPCMNREGISTRY Registry;
    VP_STATUS rc;

    rc = VBoxMPCmnRegInit(pExt, &Registry);
    if (rc != NO_ERROR)
    {
        LOGW("vps(%#x)!=NO_ERROR\n", rc);
    }

    if (u32Target == 0)
    {
        /*First name without a suffix*/
        rc = VBoxMPCmnRegSetDword(Registry, L"CustomXRes", pResolution->cx);
        if (rc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", rc);
        }
        rc = VBoxMPCmnRegSetDword(Registry, L"CustomYRes", pResolution->cy);
        if (rc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", rc);
        }
        rc = VBoxMPCmnRegSetDword(Registry, L"CustomBPP", 32); /* <- just in case for older driver usage */
        if (rc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", rc);
        }
    }
    else
    {
        wchar_t keyname[32];
        swprintf(keyname, L"CustomXRes%d", u32Target);
        rc = VBoxMPCmnRegSetDword(Registry, keyname, pResolution->cx);
        if (rc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", rc);
        }
        swprintf(keyname, L"CustomYRes%d", u32Target);
        rc = VBoxMPCmnRegSetDword(Registry, keyname, pResolution->cy);
        if (rc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", rc);
        }
        swprintf(keyname, L"CustomBPP%d", u32Target);
        rc = VBoxMPCmnRegSetDword(Registry, keyname, 32); /* <- just in case for older driver usage */
        if (rc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", rc);
        }
    }

    rc = VBoxMPCmnRegFini(Registry);
    if (rc != NO_ERROR)
    {
        LOGW("vps(%#x)!=NO_ERROR\n", rc);
    }
}

int vboxWddmVModesAdd(PVBOXMP_DEVEXT pExt, VBOXWDDM_VMODES* pModes, UINT32 u32Target, const RTRECTSIZE* pResolution, BOOLEAN fTransient)
{
    if (!pResolution->cx || !pResolution->cy)
    {
        LOGE("invalid resolution data\n");
        return VERR_INVALID_PARAMETER;
    }

    if (u32Target >= pModes->Modes.cTargets)
    {
        LOGE("invalid target id\n");
        return VERR_INVALID_PARAMETER;
    }

    ULONG vramSize = vboxWddmVramCpuVisibleSegmentSize(pExt);
    vramSize /= pExt->u.primary.commonInfo.cDisplays;
    if (!g_VBoxDisplayOnly)
    {
        /* at least two surfaces will be needed: primary & shadow */
        vramSize /= 2;
    }
    vramSize &= ~PAGE_OFFSET_MASK;

    /* prevent potensial overflow */
    if (pResolution->cx > 0x7fff
        || pResolution->cy > 0x7fff)
    {
        LOGE("resolution %dx%d insane\n", pResolution->cx, pResolution->cy);
        return VERR_INVALID_PARAMETER;
    }

    UINT32 cbSurfMem = pResolution->cx * pResolution->cy * 4;
    if (cbSurfMem > vramSize)
    {
        LOGE("resolution %dx%d too big for available VRAM (%d bytes)\n", pResolution->cx, pResolution->cy, vramSize);
        return VERR_NOT_SUPPORTED;
    }

    if (pModes->aTransientResolutions[u32Target] == CR_RSIZE2U64(*pResolution))
    {
        if (!fTransient) /* if the mode is not transient anymore, remove it from transient */
            pModes->aTransientResolutions[u32Target] = 0;
        return VINF_ALREADY_INITIALIZED;
    }

    int rc;
    BOOLEAN fTransientIfExists = false;
    if (pModes->aPendingRemoveCurResolutions[u32Target] == CR_RSIZE2U64(*pResolution))
    {
        /* no need to remove it anymore */
        pModes->aPendingRemoveCurResolutions[u32Target] = 0;
        rc = VINF_ALREADY_INITIALIZED;
        fTransientIfExists = true;
    }
    else
    {
        rc = VBoxVModesAdd(&pModes->Modes, u32Target, CR_RSIZE2U64(*pResolution));
        if (RT_FAILURE(rc))
        {
            LOGE("VBoxVModesAdd failed %d\n", rc);
            return rc;
        }
    }

    if (rc == VINF_ALREADY_INITIALIZED && !fTransientIfExists)
        return rc;

    if (fTransient)
    {
        if (pModes->aTransientResolutions[u32Target])
        {
            /* note that we can not overwrite rc here, because it holds the "existed" status, which we need to return */
            RTRECTSIZE size = CR_U642RSIZE(pModes->aTransientResolutions[u32Target]);
            int tmpRc = vboxWddmVModesRemove(pExt, pModes, u32Target, &size);
            if (RT_FAILURE(tmpRc))
            {
                LOGE("vboxWddmVModesRemove failed %d, can never happen\n", tmpRc);
                return tmpRc;
            }
        }

        pModes->aTransientResolutions[u32Target] = CR_RSIZE2U64(*pResolution);
        vboxWddmVModesSaveTransient(pExt, u32Target, pResolution);
    }

    return rc;
}

RTRECTSIZE vBoxWddmVModesCalculateDefaultSize(UINT32 surfaceWidth, UINT32 surfaceHeight)
{
    RTRECTSIZE size = { 0 };
    if (surfaceWidth == 0 || surfaceHeight == 0) {
        LOGE("Invalid surface size: width=%u, height=%u\n", surfaceWidth, surfaceHeight);
        return size;
    }
    if (surfaceWidth * 2 / 3 == surfaceHeight) {
        size.cx = DEFAULT_WIDTH_3_2;
        size.cy = DEFAULT_HEIGHT_3_2;
    } else if (surfaceWidth * 3 / 4 == surfaceHeight) {
        size.cx = DEFAULT_WIDTH_4_3;
        size.cy = DEFAULT_HEIGHT_4_3;
    } else if (surfaceWidth * 1608 / 2472 == surfaceHeight) {
        size.cx = DEFAULT_WIDTH_103_67;
        size.cy = DEFAULT_HEIGHT_103_67;
    } else {
        size.cx = DEFAULT_WIDTH_3_2;
        size.cy = DEFAULT_HEIGHT_3_2;
    }
    return size;
}

RTRECTSIZE vBoxWddmVModesCalculateSize(UINT32 surfaceWidth, UINT32 surfaceHeight)
{
    RTRECTSIZE size = { 0 };
    VBOXMPCMNREGISTRY hKey;
    NTSTATUS Status = vboxWddmRegOpenKey(&hKey, VBOXWDDM_REG_RESOLUTIONS, GENERIC_READ);
    if (Status != STATUS_SUCCESS) {
        LOGE("Use default resolution\n");
        return vBoxWddmVModesCalculateDefaultSize(surfaceWidth, surfaceHeight);
    }
    if (surfaceWidth == 0 || surfaceHeight == 0) {
        LOGE("Invalid surface size: width=%u, height=%u\n", surfaceWidth, surfaceHeight);
        if (hKey) {
            ZwClose(hKey);
        }
        return size;
    }
    DWORD width = 0;
    DWORD height = 0;
    if (surfaceWidth * 2 / 3 == surfaceHeight) {
        Status = vboxWddmRegQueryValueDword(hKey, VBOXWDDM_REG_RESOLUTIONS_WIDTH_3_2_NAME, &width);
        if (Status != STATUS_SUCCESS || width == 0) {
            LOGE("Width query failed, use default value\n");
            width = DEFAULT_WIDTH_3_2;
            height = DEFAULT_HEIGHT_3_2;
        }
        Status = vboxWddmRegQueryValueDword(hKey, VBOXWDDM_REG_RESOLUTIONS_HEIGHT_3_2_NAME, &height);
        if (Status != STATUS_SUCCESS || height == 0) {
            LOGE("Height query failed, use default value\n");
            width = DEFAULT_WIDTH_3_2;
            height = DEFAULT_HEIGHT_3_2;
        }
    }
    else if (surfaceWidth * 3 / 4 == surfaceHeight) {
        Status = vboxWddmRegQueryValueDword(hKey, VBOXWDDM_REG_RESOLUTIONS_WIDTH_4_3_NAME, &width);
        if (Status != STATUS_SUCCESS || width == 0) {
            LOGE("Width query failed, use default value\n");
            width = DEFAULT_WIDTH_4_3;
            height = DEFAULT_HEIGHT_4_3;
        }
        Status = vboxWddmRegQueryValueDword(hKey, VBOXWDDM_REG_RESOLUTIONS_HEIGHT_4_3_NAME, &height);
        if (Status != STATUS_SUCCESS || height == 0) {
            LOGE("Height query failed, use default value\n");
            width = DEFAULT_WIDTH_4_3;
            height = DEFAULT_HEIGHT_4_3;
        }
    }
    else if (surfaceWidth * 1608 / 2472 == surfaceHeight) {
        Status = vboxWddmRegQueryValueDword(hKey, VBOXWDDM_REG_RESOLUTIONS_WIDTH_103_67_NAME, &width);
        if (Status != STATUS_SUCCESS || width == 0) {
            LOGE("Width query failed, use default value\n");
            width = DEFAULT_WIDTH_103_67;
            height = DEFAULT_HEIGHT_103_67;
        }
        Status = vboxWddmRegQueryValueDword(hKey, VBOXWDDM_REG_RESOLUTIONS_HEIGHT_103_67_NAME, &height);
        if (Status != STATUS_SUCCESS || height == 0) {
            LOGE("Height query failed, use default value\n");
            width = DEFAULT_WIDTH_103_67;
            height = DEFAULT_HEIGHT_103_67;
        }
    }
    LOGE("Width is %d, height is %d\n", width, height);
    size.cx = (UINT32)width;
    size.cy = (UINT32)height;
    if (hKey) {
        ZwClose(hKey);
    }
    return size;
}

int vBoxWddmVModesInitForTarget(PVBOXMP_DEVEXT pExt, VBOXWDDM_VMODES *pModes, UINT32 u32Target, BOOLEAN initialed)
{
    PVBOXWDDM_EXT_VMSVGA pSvga = pExt->pGa->hw.pSvga;
    UINT32 surface_width = SVGARegRead(pSvga, SVGA_REG_WIDTH);
    UINT32 surface_height = SVGARegRead(pSvga, SVGA_REG_HEIGHT);
    vboxInitResolutionsWithSurfaceSize(surface_width, surface_height);

    if (!initialed) {
        vboxWddmVModesAdd(pExt, pModes, u32Target, &vBoxWddmVModesCalculateSize(surface_width, surface_height), FALSE);
    } else {
        for (UINT32 i = 0; i < g_VBoxBuiltinResolutionsSize; ++i)
        {
            vboxWddmVModesAdd(pExt, pModes, u32Target, &g_VBoxBuiltinResolutions[i], FALSE);
        }
        if (pExt->aTargets[u32Target].Size.cx)
        {
            vboxWddmVModesAdd(pExt, pModes, u32Target, &pExt->aTargets[u32Target].Size, TRUE);
        }
    }

    // no need to restore custom resolution
    return VINF_SUCCESS;

    /* Check registry for manually added modes, up to 128 entries is supported
     * Give up on the first error encountered.
     */
    VBOXMPCMNREGISTRY Registry;
    VP_STATUS vpRc;

    vpRc = VBoxMPCmnRegInit(pExt, &Registry);
    if (vpRc != NO_ERROR)
    {
        LOGE("VBoxMPCmnRegInit failed %d, ignore\n", vpRc);
        return VINF_SUCCESS;
    }

    UINT32 CustomXRes = 0, CustomYRes = 0;

    if (u32Target == 0)
    {
        /*First name without a suffix*/
        vpRc = VBoxMPCmnRegQueryDword(Registry, L"CustomXRes", &CustomXRes);
        if (vpRc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", vpRc);
        }
        vpRc = VBoxMPCmnRegQueryDword(Registry, L"CustomYRes", &CustomYRes);
        if (vpRc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", vpRc);
        }
    }
    else
    {
        wchar_t keyname[32];
        swprintf(keyname, L"CustomXRes%d", u32Target);
        vpRc = VBoxMPCmnRegQueryDword(Registry, keyname, &CustomXRes);
        if (vpRc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", vpRc);
        }
        swprintf(keyname, L"CustomYRes%d", u32Target);
        vpRc = VBoxMPCmnRegQueryDword(Registry, keyname, &CustomYRes);
        if (vpRc != NO_ERROR)
        {
            LOGW("vps(%#x)!=NO_ERROR\n", vpRc);
        }
    }

    LOGI("got stored custom resolution[%d] %dx%dx\n", u32Target, CustomXRes, CustomYRes);

    if (CustomXRes || CustomYRes)
    {
        if (CustomXRes == 0)
            CustomXRes = pExt->aTargets[u32Target].Size.cx ? pExt->aTargets[u32Target].Size.cx : 800;
        if (CustomYRes == 0)
            CustomYRes = pExt->aTargets[u32Target].Size.cy ? pExt->aTargets[u32Target].Size.cy : 600;

        RTRECTSIZE Resolution = {CustomXRes, CustomYRes};
        vboxWddmVModesAdd(pExt, pModes, u32Target, &Resolution, TRUE);
    }


    for (int curKey=0; curKey<128; curKey++)
    {
        wchar_t keyname[24];

        swprintf(keyname, L"CustomMode%dWidth", curKey);
        vpRc = VBoxMPCmnRegQueryDword(Registry, keyname, &CustomXRes);
        if (vpRc != NO_ERROR)
        {
            break;
        }

        swprintf(keyname, L"CustomMode%dHeight", curKey);
        vpRc = VBoxMPCmnRegQueryDword(Registry, keyname, &CustomYRes);
        if (vpRc != NO_ERROR)
        {
            break;
        }

        LOGI("got custom mode[%u]=%ux%u\n", curKey, CustomXRes, CustomYRes);

        /* round down width to be a multiple of 8 if necessary */
        if (!VBoxCommonFromDeviceExt(pExt)->fAnyX)
        {
            CustomXRes &= 0xFFF8;
        }

        LOGI("adding video mode from registry.\n");

        RTRECTSIZE Resolution = {CustomXRes, CustomYRes};

        vboxWddmVModesAdd(pExt, pModes, u32Target, &Resolution, FALSE);
    }

    vpRc = VBoxMPCmnRegFini(Registry);

    return VINF_SUCCESS;
}

static VBOXWDDM_VMODES g_VBoxWddmVModes;

void VBoxWddmVModesCleanup()
{
    VBOXWDDM_VMODES *pModes = &g_VBoxWddmVModes;
    vboxWddmVModesCleanup(pModes);
}

void VBoxWddmVModesRemoveAll(PVBOXMP_DEVEXT pExt)
{
    VBOXWDDM_VMODES *pModes = &g_VBoxWddmVModes;

    for (UINT32 i = 0; i < g_VBoxBuiltinResolutionsSize; ++i)
    {
        vboxWddmVModesRemove(pExt, pModes, (UINT32)i, &g_VBoxBuiltinResolutions[i]);
    }
}

int VBoxWddmVModesInit(PVBOXMP_DEVEXT pExt, BOOLEAN initialed)
{
    VBOXWDDM_VMODES *pModes = &g_VBoxWddmVModes;

    vboxWddmVModesInit(pModes, VBoxCommonFromDeviceExt(pExt)->cDisplays);

    int rc;

    for (UINT32 i = 0; i < VBoxCommonFromDeviceExt(pExt)->cDisplays; ++i)
    {
        rc = vBoxWddmVModesInitForTarget(pExt, pModes, i, initialed);
        if (RT_FAILURE(rc))
        {
            LOGE("vBoxWddmVModesInitForTarget failed %d\n", rc);
            return rc;
        }
    }

    return VINF_SUCCESS;
}

const CR_SORTARRAY* VBoxWddmVModesGet(PVBOXMP_DEVEXT pExt, UINT32 u32Target)
{
    if (u32Target >= (UINT32)VBoxCommonFromDeviceExt(pExt)->cDisplays)
    {
        LOGW("invalid target: %u\n", u32Target);
        return NULL;
    }

    return &g_VBoxWddmVModes.Modes.aTargets[u32Target];
}

int VBoxWddmVModesRemove(PVBOXMP_DEVEXT pExt, UINT32 u32Target, const RTRECTSIZE *pResolution)
{
    return vboxWddmVModesRemove(pExt, &g_VBoxWddmVModes, u32Target, pResolution);
}

int VBoxWddmVModesAdd(PVBOXMP_DEVEXT pExt, UINT32 u32Target, const RTRECTSIZE *pResolution, BOOLEAN fTrancient)
{
    return vboxWddmVModesAdd(pExt, &g_VBoxWddmVModes, u32Target, pResolution, fTrancient);
}
